using System;
using System.Threading;
using ch.ethz.ssh2.util;
namespace ch.ethz.ssh2
{
	
	/// <summary> A <code>StreamGobbler</code> is an InputStream that uses an internal worker
	/// thread to constantly consume input from another InputStream. It uses a buffer
	/// to store the consumed data. The buffer size is automatically adjusted, if needed.
	/// <p>
	/// This class is sometimes very convenient - if you wrap a session's STDOUT and STDERR
	/// InputStreams with instances of this class, then you don't have to bother about
	/// the shared window of STDOUT and STDERR in the low level SSH-2 protocol,
	/// since all arriving data will be immediatelly consumed by the worker threads.
	/// Also, as a side effect, the streams will be buffered (e.g., single byte
	/// read() operations are faster).
	/// <p>
	/// Other SSH for Java libraries include this functionality by default in
	/// their STDOUT and STDERR InputStream implementations, however, please be aware
	/// that this approach has also a downside:
	/// <p>
	/// If you do not call the StreamGobbler's <code>read()</code> method often enough
	/// and the peer is constantly sending huge amounts of data, then you will sooner or later
	/// encounter a low memory situation due to the aggregated data (well, it also depends on the Java heap size).
	/// Joe Average will like this class anyway - a paranoid programmer would never use such an approach.
	/// <p>
	/// The term "StreamGobbler" was taken from an article called "When Runtime.exec() won't",
	/// see http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html.
	/// 
	/// </summary>
	/// <author>  Christian Plattner
	/// </author>
	/// <version>  2.50, 03/15/10
	/// </version>
	
	public class StreamGobbler:System.IO.Stream
	{		
		private System.IO.Stream input;
		private Thread thread;
		
		private System.Object synchronizer = new System.Object();
		
		private bool isEOF = false;
		private bool isClosed = false;
		private System.IO.IOException exception = null;
		
		private byte[] buffer = new byte[2048];
		private int read_pos = 0;
		private int write_pos = 0;
		
		public StreamGobbler(System.IO.Stream input)
		{
			this.input = input;
            thread = new Thread(new ThreadStart(GobblerThread));
			thread.IsBackground = true;
			thread.Start();
		}

        private void GobblerThread()
        {
            byte[] buff = new byte[8192];

            while (true)
            {
                try
                {
                    int avail = (input is ch.ethz.ssh2.StreamGobbler || input is ch.ethz.ssh2.channel.ChannelInputStream) ? (int)Utility.InvokeMethodAsVirtual(input, "read", new System.Object[] { buff }) : Utility.ReadInput(input, buff, 0, buff.Length);

                    lock (synchronizer)
                    {
                        if (avail <= 0)
                        {
                            isEOF = true;
                            System.Threading.Monitor.PulseAll(synchronizer);
                            break;
                        }

                        int space_available = buffer.Length - write_pos;

                        if (space_available < avail)
                        {
                            /* compact/resize buffer */

                            int unread_size = write_pos - read_pos;
                            int need_space = unread_size + avail;

                            byte[] new_buffer = buffer;

                            if (need_space > buffer.Length)
                            {
                                int inc = need_space / 3;
                                inc = (inc < 256) ? 256 : inc;
                                inc = (inc > 8192) ? 8192 : inc;
                                new_buffer = new byte[need_space + inc];
                            }

                            if (unread_size > 0)
                                Array.Copy(buffer, read_pos, new_buffer, 0, unread_size);

                            buffer = new_buffer;

                            read_pos = 0;
                            write_pos = unread_size;
                        }

                        Array.Copy(buff, 0, buffer, write_pos, avail);
                        write_pos += avail;

                        System.Threading.Monitor.PulseAll(synchronizer);
                    }
                }
                catch (System.IO.IOException e)
                {
                    lock (synchronizer)
                    {
                        exception = e;
                        System.Threading.Monitor.PulseAll(synchronizer);
                        break;
                    }
                }
            }
        }
		
		public override int ReadByte()
		{
			lock (synchronizer)
			{
				if (isClosed)
					throw new System.IO.IOException("This StreamGobbler is closed.");
				
				while (read_pos == write_pos)
				{
					if (exception != null)
						throw exception;
					
					if (isEOF)
						return - 1;
					
					try
					{
						System.Threading.Monitor.Wait(synchronizer);
					}
					catch (System.Threading.ThreadInterruptedException e)
					{
                        Thread.CurrentThread.Interrupt();
					}
				}
				
				int b = buffer[read_pos++] & 0xff;
				
				return b;
			}
		}
		
		//UPGRADE_NOTE: The equivalent of method 'java.io.InputStream.available' is not an override method. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1143'"
		public int available()
		{
			lock (synchronizer)
			{
				if (isClosed)
					throw new System.IO.IOException("This StreamGobbler is closed.");
				
				return write_pos - read_pos;
			}
		}
		
		//UPGRADE_NOTE: The equivalent of method 'java.io.InputStream.read' is not an override method. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1143'"
		public int read(byte[] b)
		{
			return read(b, 0, b.Length);
		}
		
		public override void  Close()
		{
			lock (synchronizer)
			{
				if (isClosed)
					return ;
				isClosed = true;
				isEOF = true;
				System.Threading.Monitor.PulseAll(synchronizer);
				input.Close();
			}
		}
		
		//UPGRADE_NOTE: The equivalent of method 'java.io.InputStream.read' is not an override method. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1143'"
		public int read(byte[] b, int off, int len)
		{
			if (b == null)
				throw new System.NullReferenceException();
			
			if ((off < 0) || (len < 0) || ((off + len) > b.Length) || ((off + len) < 0) || (off > b.Length))
				throw new System.IndexOutOfRangeException();
			
			if (len == 0)
				return 0;
			
			lock (synchronizer)
			{
				if (isClosed)
					throw new System.IO.IOException("This StreamGobbler is closed.");
				
				while (read_pos == write_pos)
				{
					if (exception != null)
						throw exception;
					
					if (isEOF)
						return - 1;
					
					try
					{
						System.Threading.Monitor.Wait(synchronizer);
					}
					catch (System.Threading.ThreadInterruptedException e)
					{
						Thread.CurrentThread.Interrupt();
					}
				}
				
				int avail = write_pos - read_pos;
				
				avail = (avail > len)?len:avail;
				
				Array.Copy(buffer, read_pos, b, off, avail);
				
				read_pos += avail;
				
				return avail;
			}
		}
		//UPGRADE_TODO: The following method was automatically generated and it must be implemented in order to preserve the class logic. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1232'"
		public override void  Flush()
		{
		}
		//UPGRADE_TODO: The following method was automatically generated and it must be implemented in order to preserve the class logic. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1232'"
		public override System.Int64 Seek(System.Int64 offset, System.IO.SeekOrigin origin)
		{
			return 0;
		}
		//UPGRADE_TODO: The following method was automatically generated and it must be implemented in order to preserve the class logic. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1232'"
		public override void  SetLength(System.Int64 value)
		{
		}
		//UPGRADE_TODO: The following method was automatically generated and it must be implemented in order to preserve the class logic. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1232'"
		public override System.Int32 Read(System.Byte[] buffer, System.Int32 offset, System.Int32 count)
		{
			return 0;
		}
		//UPGRADE_TODO: The following method was automatically generated and it must be implemented in order to preserve the class logic. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1232'"
		public override void  Write(System.Byte[] buffer, System.Int32 offset, System.Int32 count)
		{
		}
		//UPGRADE_TODO: The following property was automatically generated and it must be implemented in order to preserve the class logic. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1232'"
		public override System.Boolean CanRead
		{
			get
			{
				return false;
			}
			
		}
		//UPGRADE_TODO: The following property was automatically generated and it must be implemented in order to preserve the class logic. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1232'"
		public override System.Boolean CanSeek
		{
			get
			{
				return false;
			}
			
		}
		//UPGRADE_TODO: The following property was automatically generated and it must be implemented in order to preserve the class logic. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1232'"
		public override System.Boolean CanWrite
		{
			get
			{
				return false;
			}
			
		}
		//UPGRADE_TODO: The following property was automatically generated and it must be implemented in order to preserve the class logic. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1232'"
		public override System.Int64 Length
		{
			get
			{
				return 0;
			}
			
		}
		//UPGRADE_TODO: The following property was automatically generated and it must be implemented in order to preserve the class logic. "ms-help://MS.VSCC.v80/dv_commoner/local/redirect.htm?index='!DefaultContextWindowIndex'&keyword='jlca1232'"
		public override System.Int64 Position
		{
			get
			{
				return 0;
			}
			
			set
			{
			}
			
		}
	}
}